<?php
require_once(dirname(__FILE__).'/../Abstract.php');
class AbstractORM extends AbstractModel 
{
	/*Constants*/
	const PATTERN_GET_BY='/getBy(.*)/';
	const PATTERN_GET_ALL_BY='/getAllBy(.*)/';
	const PATTERN_GET_ALL_LIKE='/getsAllLike(.*)/';
	const PATTERN_GET_ALL='/getAllRows/';
	
	const PATTERN_FETCH_BY='/fetchBy(.*)/';
	const PATTERN_FETCH_ALL_BY='/fetchAllBy(.*)/';
	const PATTERN_FETCH_ALL_LIKE='/fetchAllLike(.*)/';
	const PATTERN_FETCH_ALL='/fetchAllRows/';
	
	const PATTERN_REMOVE_BY='/removeBy(.*)/';
	const PATTERN_REMOVE_LIKE='/removeAllLike(.*)/';
	
	const PATTERN_COUNT_BY='/countBy(.*)/';
	const PATTERN_COUNT_LIKE='/countAllLike(.*)/';
	const PATTERN_COUNT_ALL='/countAll/';
	
	/*Protected Properties*/
	protected $_name      = NULL;
	protected $_lang_name = NULL;
	protected $_primary   = 'id';
    
	protected $_order     = NULL;
	protected $_limit     = NULL;
    
	protected $_fields    = array();

	/*Construct*/
    public function __construct()
    {
        // Call the Model constructor
        parent::__construct();
    }
    
    public function getTableName () {return $this->_name;}
    public function getPrimaryKey () {return $this->_primary;}
    
	public function save($record) 
    {
		$this->_primary = (is_array($this->_primary))?array_pop($this->_primary):$this->_primary;
        
        // Lang field
        if ($this->_lang_name) {
            $langColumns = $this->getLangFieldsName();
            $recordLang = array();
            foreach ($record AS $k=>$v) {
                if (in_array($k, $langColumns) && is_array($v) && $this->_primary!=$v) {
                    foreach (NZ_Config::$languages  AS $lang => $language) {
                        if (isset($v[$lang]))
                            $recordLang[$lang][$k] = $v[$lang];
                        else 
                            $recordLang[$lang][$k] = '';
                    }
                    
                    unset($record[$k]);
                }
            }
        }
            
        // Automatic field
        if ($this->hasField('date_upd')) {
            $record['date_upd'] = @date('Y-m-d H:i:s');
        }
        
        // Begin transaction
        $this->db->trans_begin();
        
        // Updates
    	if(isset($record[$this->_primary])){
            $id = $record[$this->_primary]; unset($record[$this->_primary]);
            
            // Update default fields
    	    $this->db->flush_cache();
    	    $this->db->where("`{$this->_primary}`", $id);
            $this->db->update($this->_name, $record);
            
            // Update language  fields
            if ($this->_lang_name) {
                foreach (NZ_Config::$languages  AS $lang => $language) {
                    $this->db->flush_cache();
                    $this->db->where(array("{$this->_primary}"=>$id, "lang"=>$lang));
                    $this->db->update($this->_lang_name, $recordLang[$lang]);
                }
            }
    	} else {
            // Automatic field
            if ($this->hasField('date_add')) {
                $record['date_add'] = @date('Y-m-d H:i:s');
            }
            
            // insert
            $this->db->insert($this->_name, $record);
            $id = $this->db->insert_id();
            
            // Update language  fields
            if ($this->_lang_name) {
                foreach (NZ_Config::$languages  AS $lang => $language) {
                    $this->db->flush_cache();
                    $recordLang[$lang][$this->_primary] = $id;
                    $recordLang[$lang]["lang"]          = $lang;
                    $this->db->insert($this->_lang_name, $recordLang[$lang]);
                }
            }
    	}
        
        // End transaction
        if ($this->db->trans_status() === FALSE) {
            $this->db->trans_rollback();
            return false;
        } else {
            $this->db->trans_commit();
        }
        return $id;
    }
    
	public function exec($sql)
    {
		return $this->db->query($sql)->result();
	}
    public function getFieldsName ()
    {
        if (empty($this->_fields)) {
            $sql = 'SHOW FULL FIELDS FROM `'. $this->_name .'`;';
    		$this->_fields = $this->exec($sql);
        }
        return array_map(create_function('$row', 'return $row->Field;'), $this->_fields);
    }
    
    public function getLangFieldsName ()
    {
        if (isset($this->_lang_name )) {
            if (empty($this->_lang_fields)) {
                $sql = 'SHOW FULL FIELDS FROM `'. $this->_lang_name .'`;';
        		$this->_lang_fields = $this->exec($sql);
            }
            return array_map(create_function('$row', 'return $row->Field;'), $this->_lang_fields);
        }
        return array();
    }
    
    public function hasField ($fieldName)
    {
        return in_array($fieldName, $this->getFieldsName());
    }
    
    public function hasLangField ($fieldName)
    {
        return in_array($fieldName, $this->getLangFieldsName());
    }
    
    public function hasAnyField ($fieldName)
    {
        return in_array($fieldName, array_merge($this->getFieldsName(), $this->getLangFieldsName()));
    }
    
	public function getById($id, $lang = NULL)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhere(array($this->_primary => $id), array());
		$this->_sqlOrder(); $this->_sqlLimit();
        return $this->_getWithLang($lang);
	}
    
	public function getAll($lang = NULL)
    {
		$this->db->from($this->_name);
		$this->_sqlOrder(); $this->_sqlLimit();
        return $this->_getAllWithLang($lang);
	}
	
	/*Private Functions*/
	/**
	 * Transform the name of the fields from "ParentId" to "parent_id"
	 * @param array $keys
	 * @return array
	 */ 
	private static function normalizeKeys($keys)
    {
		if (!is_array($keys))
			return strtolower(substr(preg_replace('/([A-Z]+)/', '_\1',$keys),1));
		foreach($keys as $k=>$v) $keys[$k] = strtolower(substr(preg_replace('/([A-Z]+)/', '_\1',$v),1));
		return $keys;
	}
	/**
	 * Get the name of the fields and separator from the name of the function
	 * @param string $name
	 * @return array
	 */
	private static function splitKeys($name)
    {
		$where = array();
		preg_match_all('/(And|Or)[A-Z]/', $name, $where);
		$name = preg_replace('/(And|Or)([A-Z])/', '###\2', $name);
		$keys = explode('###', $name);
		return array(
			'keys'=>$keys,
			'where'=>$where
		);
	}
	/**
	 * Add Order By to the query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlOrder()
    {
        if ($this->_order !== NULL) 
			if (!is_array($this->_order)) $this->db->order_by($this->_order, "asc");
			else foreach ($this->_order as $order) $this->db->order_by($order);
		return $this->db;
	}
	/**
	 * Add Limit to the query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlLimit()
    {
		if ($this->_limit !== NULL)
			if (!is_array($this->_limit)) $this->db->limit($this->_limit);
			else $this->db->limit($this->_limit[0], $this->_limit[1]);
		return $this->db;
	}
	/**
	 * Add conditions to a get query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlAddGetWhere($fields, $where)
    {
		foreach($fields as $field=>$value)
			if (is_array($value) && !empty($value))
				$this->db->where_in("`$field`", $value);
			elseif ($value===null) $this->db->where("`$field`", NULL);
			elseif (empty($value)) $this->db->where("1=0");
			else $this->db->where("`$field`", $value);
		return $this->db;
	}
	/**
	 * Add LIKE conditions to a get query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlAddGetWhereLike($fields, $where)
    {
		foreach($fields as $field=>$value)
			if (is_array($value) && !empty($value))
                $this->db->where_in("`$field`", $value);
			elseif ($value===null) $this->db->where("`$field`", NULL);
			elseif (empty($value)) $this->db->where("1=0");
			else $this->db->like("`$field`", $value);
		return $this->db;
	}
	/**
	 * Add conditions to a fetch query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlAddFetchWhere($fields, $where)
    {
		$name = get_class($this);
		foreach($fields as $field=>$value)
			if (is_array($value) && !empty($value))
				$this->db->where_in("`$name`.`$field`", $value);
			elseif ($value===null) $this->db->where("`$name`.`$field`", NULL);
			elseif (empty($value)) $this->db->where("1=0");
			else $this->db->where("`$name`.`$field`", $value);
		return $this->db;
	}
	/**
	 * Add LIKE conditions to a fetch query
	 * @param Zend_Db_Select $sql
	 * @return Zend_Db_Select
	 */
	protected function _sqlAddFetchWhereLike($fields, $where)
    {
		$name = get_class($this);
		foreach($fields as $field=>$value)
			if (is_array($value) && !empty($value))
				$this->db->where_in("`$name`.`$field`", $value);
			elseif ($value===null) $this->db->where("`$name`.`$field`", NULL);
			elseif (empty($value)) $this->db->where("1=0");
			else $this->db->like("`$name`.`$field`", $value);
		return $this->db;
	}
    
    protected function _getWithLang ($lang = null) 
    {
        // If the lang is selected
        if ($lang && $this->_lang_name) {
            // A language is selected
            $this->db->join($this->_lang_name, "{$this->_lang_name}.{$this->_primary}={$this->_name}.{$this->_primary} AND {$this->_lang_name}.lang='$lang'", 'left');
            $result = $this->db->get()->row();
        } elseif ($this->_lang_name) {
            // No languages was seleted, so get all language
            $result = $this->db->get()->row();
            if (isset($result->{$this->_primary})) {
                $id = (int)$result->{$this->_primary};
                
                // Get the value for each language
                $query = $this->db->query("SELECT * FROM `{$this->_lang_name}` WHERE `{$this->_primary}` = $id");
                foreach ($query->result() as $row)
                {
                    foreach ($this->getLangFieldsName() AS $fieldLang) {
                        if ($fieldLang != 'lang') {
                            if (!isset($result->$fieldLang)) {
                                $result->$fieldLang = array();
                            }
                            if (is_array($result->$fieldLang)) {
                                $result->$fieldLang = array_merge($result->$fieldLang, array($row->lang => $row->$fieldLang));
                            }
                        }
                    }
                }
            }
        } else {
            $result = $this->db->get()->row();
        }
        
		return $result;
    }
    
    protected function _getAllWithLang ($lang = NULL) 
    {
        // If the lang is selected
        if ($lang && $this->_lang_name) {
            // A language is selected
            $this->db->join($this->_lang_name, "{$this->_lang_name}.{$this->_primary}={$this->_name}.{$this->_primary} AND {$this->_lang_name}.lang='$lang'", 'left');
            $result = $this->db->get()->result();
        } elseif ($this->_lang_name) {
            // No languages was seleted, so get all language
            $result = $this->db->get()->result();
            
            foreach ($result AS &$resultItem) {
                if (!empty($resultItem->{$this->_primary})) {
                    $id = (int)$resultItem->{$this->_primary};
                
                    // Get the value for each language
                    $query = $this->db->query("SELECT * FROM `{$this->_lang_name}` WHERE `{$this->_primary}` = $id");
                    foreach ($query->result() as $row)
                    {
                        foreach ($this->getLangFieldsName() AS $fieldLang) {
                            if ($fieldLang != 'lang') {
                                if (!isset($resultItem->$fieldLang)) {
                                    $resultItem->$fieldLang = array();
                                }
                                if (is_array($resultItem->$fieldLang)) {
                                    $resultItem->$fieldLang = array_merge($resultItem->$fieldLang, array($row->lang => $row->$fieldLang));
                                }
                            }
                        }
                    }
                }
            }
        } else {
            $result = $this->db->get()->result();
        }
        
		return $result;
    }
    
	/**
	 * Get a record by fields
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Row
	 */
	protected function __getByFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhere($fields, $where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->row();
	}
	/**
	 * Get all records by fields
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Rowset
	 */
	protected function __getAllByFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhere($fields, $where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->result();
	}
	/**
	 * Get all records by fields using LIKE
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Rowset
	 */
	protected function __getAllLikeFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhereLike($fields, $where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->result();
	}
	/**
	 * Count the number of records based on the given fields
	 * @param array $fields
	 * @param array $where
	 * @return int
	 */
	protected function __countByFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhere($fields, $where);
		return $db->count_all_results();
	}
	/**
	 * Count the number of records based on the given fields using LIKE
	 * @param array $fields
	 * @param array $where
	 * @return int
	 */
	protected function __countAllLikeFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddGetWhereLike($fields, $where);
		return $db->count_all_results();
	}
	/**
	 * Fetch a record by fields
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Row
	 */
	protected function __fetchByFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddFetchWhere($fields,$where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->row();
	}
	/**
	 * Fetch all records by fields
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Rowset
	 */
	protected function __fetchAllByFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddFetchWhere($fields,$where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->result();
	}
	/**
	 * Fetch all records by fields using LIKE
	 * @param array $fields
	 * @param array $where
	 * @return Zend_Db_Table_Rowset
	 */
	protected function __fetchAllLikeFields($fields=array(), $where=null)
    {
		$this->db->from($this->_name);
		$this->_sqlAddFetchWhereLike($fields, $where);
		$this->_sqlOrder(); $this->_sqlLimit();
		return $this->db->get()->result();
	}
	/**
	 * Remove records by fields
	 * @param array $fields
	 * @param array $where
	 * @return Number
	 */
	protected function __removeByFields($fields=array(), $where=null)
    {
		foreach($fields as $field=>$value){
			if (is_array($value) && empty($value)) return false;
			if (is_array($value))
                $this->db->where_in("`$field`", $value);
			elseif(is_string($value)) $this->db->where("`$field`", addslashes($value));
			elseif ($value===null) $this->db->where("`$field`", NULL);
			else $this->db->where("`$field`", addslashes($value));
		}
		return $this->db->delete($this->_name);
	}
	/**
	 * Remove all records using LIKE
	 * @param array $fields
	 * @param array $where
	 * @return Number
	 */
	protected function __removeLikeFields($fields=array(), $where=null)
    {
		foreach($fields as $field=>$value){
			if (is_array($value) && empty($value)) return false;
			if (is_array($value))
				$this->db->where_in("`$field`", $value);
			elseif(is_string($value)) $this->db->like("`$field`", addslashes($value));
			elseif ($value===null) $this->db->where("`$field`", NULL);
			else $this->db->where("`$field`", addslashes($value));
		}
		return $this->db->delete($this->_name);
	}
	/**
	 * Parse the name of the called function and test if it matches the current patterns 
	 * and return the appropriate result.
	 * @param string $name
	 * @param array $arguments
	 * @return mixed
	 */
	public function __call($name, $arguments) 
    {
        preg_match(self::PATTERN_GET_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__getByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_GET_ALL_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__getAllByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_GET_ALL_LIKE, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__getAllLikeFields'), array($args, $where));
		}
		preg_match(self::PATTERN_GET_ALL, $name, $matches);
		if (count($matches)){
			return call_user_func_array(array($this, '__getAllByFields'), array());
		}
		preg_match(self::PATTERN_FETCH_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__fetchByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_FETCH_ALL_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__fetchAllByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_FETCH_ALL_LIKE, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__fetchAllLikeFields'), array($args, $where));
		}
		preg_match(self::PATTERN_FETCH_ALL, $name, $matches);
		if (count($matches)){
			return call_user_func_array(array($this, '__fetchAllByFields'), array());
		}
		preg_match(self::PATTERN_REMOVE_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__removeByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_REMOVE_LIKE, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__removeLikeFields'), array($args, $where));
		}
		preg_match(self::PATTERN_COUNT_BY, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args=array();
			if (is_array($keys) && !empty($keys[0]) && is_array($arguments))
				$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__countByFields'), array($args, $where));
		}
		preg_match(self::PATTERN_COUNT_LIKE, $name, $matches);
		if (count($matches)){
			$keys = self::splitKeys($matches[1]); $where=$keys['where']; $keys=$keys['keys'];
			$keys = self::normalizeKeys($keys);
			$args=array();
			if (is_array($keys) && !empty($keys[0]) && is_array($arguments))
				$args = array_combine($keys, $arguments);
			return call_user_func_array(array($this, '__countAllLikeFields'), array($args, $where));
		}
		preg_match(self::PATTERN_COUNT_ALL, $name, $matches);
		if (count($matches)){
			return call_user_func_array(array($this, '__countByFields'), array());
		}
    }
}


